﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Reflection;
using System.Collections;
using BeanboxSoftware.BeanProxy.Internals;

namespace BeanboxSoftware.BeanProxy.Tests
{
  [TestClass]
  public class ProxyTest
  {
    [TestMethod]
    public void NonVirtualPropTest()
    {
      var proxy = ProxyManager.CreateProxy<InterfaceOne>();

      TestUtils.AssertExceptionThrown<NotImplementedException>(() =>
      {
        ProxyManager.GetProp<int>(proxy, () => { return 1; });
      });

      TestUtils.AssertExceptionThrown<NotImplementedException>(() =>
      {
        ProxyManager.GetProp<int>(proxy, (v) => { });
      });
    }

    [TestMethod]
    public void ComplexBuildTest()
    {
      var proxy = ProxyManager.CreateProxy<ComplexClassToProxy<int, double>>();

      Assert.IsNotNull(proxy);

      ProxyManager.GetAction(proxy.Throws).IsReal = false;
      proxy.Throws();

      Assert.AreEqual(1, ProxyManager.GetAction(proxy.Throws).Count, "Throws should have been called once");

      ProxyManager.GetAction(proxy.Throws).IsReal = true;
      Assert.IsTrue(ProxyManager.GetAction(proxy.Throws).IsReal);
      ProxyManager.GetAction(proxy.Throws).Count = 0;
      Assert.AreEqual(0, ProxyManager.GetAction(proxy.Throws).Count);

      try
      {
        proxy.Throws();
        Assert.Fail("Throws should be real");
      }
      catch (NotImplementedException)
      {
      }

      Assert.AreEqual(1, ProxyManager.GetAction(proxy.Throws).Count, "Throws should have been called once");

      ProxyManager.GetAction(proxy.ActionMethod).IsReal = false;
      ProxyManager.GetAction<int>(proxy.Action1Method).IsReal = false;
      ProxyManager.GetAction<int, int>(proxy.Action2Method).IsReal = false;
      ProxyManager.GetAction<int, int, int>(proxy.Action3Method).IsReal = false;
      ProxyManager.GetAction<int, int, int, int>(proxy.Action4Method).IsReal = false;
      ProxyManager.GetAction<int, int, int, int, int>(proxy.Action5Method).IsReal = false;

      ProxyManager.GetFunc<int>(proxy.FuncMethod).Return = -1;
      ProxyManager.GetFunc<int, int>(proxy.Func1Method).Return = -2;
      ProxyManager.GetFunc<int, int, int>(proxy.Func2Method).Return = -3;
      ProxyManager.GetFunc<int, int, int, int>(proxy.Func3Method).Return = -4;
      ProxyManager.GetFunc<int, int, int, int, int>(proxy.Func4Method).Return = -5;
      ProxyManager.GetFunc<int, int, int, int, int, int>(proxy.Func5Method).Return = -6;

      var action5Config = ProxyManager.GetAction<int, int, int, int, int>(proxy.Action5Method);
      var func5Config = ProxyManager.GetFunc<int, int, int, int, int, int>(proxy.Func5Method);

      proxy.ActionMethod();
      proxy.Action1Method(1);
      proxy.Action2Method(1, 2);
      proxy.Action3Method(1, 2, 3);
      proxy.Action4Method(1, 2, 3, 4);
      proxy.Action5Method(1, 2, 3, 4, 5);
      Assert.AreEqual(-1, proxy.FuncMethod());
      Assert.AreEqual(-2, proxy.Func1Method(1));
      Assert.AreEqual(-3, proxy.Func2Method(1, 2));
      Assert.AreEqual(-4, proxy.Func3Method(1, 2, 3));
      Assert.AreEqual(-5, proxy.Func4Method(1, 2, 3, 4));
      Assert.AreEqual(-6, proxy.Func5Method(1, 2, 3, 4, 5));

      Assert.AreEqual(1, action5Config.Param1);
      Assert.AreEqual(2, action5Config.Param2);
      Assert.AreEqual(3, action5Config.Param3);
      Assert.AreEqual(4, action5Config.Param4);
      Assert.AreEqual(5, action5Config.Param5);

      Assert.AreEqual(1, func5Config.Param1);
      Assert.AreEqual(2, func5Config.Param2);
      Assert.AreEqual(3, func5Config.Param3);
      Assert.AreEqual(4, func5Config.Param4);
      Assert.AreEqual(5, func5Config.Param5);

      Assert.AreEqual(ProxyManager.GetAction(proxy.ActionMethod).Access, ProxyManager.LookupAction(proxy, "ActionMethod").Access, "ActionMethod Lookup");
      Assert.AreEqual(ProxyManager.GetAction<int>(proxy.Action1Method).Access, ProxyManager.LookupAction<int>(proxy, "Action1Method").Access, "Action1Method Lookup");
      Assert.AreEqual(ProxyManager.GetAction<int, int>(proxy.Action2Method).Access, ProxyManager.LookupAction<int, int>(proxy, "Action2Method").Access, "Action2Method Lookup");
      Assert.AreEqual(ProxyManager.GetAction<int, int, int>(proxy.Action3Method).Access, ProxyManager.LookupAction<int, int, int>(proxy, "Action3Method").Access, "Action3Method Lookup");
      Assert.AreEqual(ProxyManager.GetAction<int, int, int, int>(proxy.Action4Method).Access, ProxyManager.LookupAction<int, int, int, int>(proxy, "Action4Method").Access, "Action4Method Lookup");
      Assert.AreEqual(ProxyManager.GetAction<int, int, int, int, int>(proxy.Action5Method).Access, ProxyManager.LookupAction<int, int, int, int, int>(proxy, "Action5Method").Access, "Action5Method Lookup");

      Assert.AreEqual(ProxyManager.GetFunc<int>(proxy.FuncMethod).Access, ProxyManager.LookupFunc<int>(proxy, "FuncMethod").Access, "FuncMethod Lookup");
      Assert.AreEqual(ProxyManager.GetFunc<int, int>(proxy.Func1Method).Access, ProxyManager.LookupFunc<int, int>(proxy, "Func1Method").Access, "Func1Method Lookup");
      Assert.AreEqual(ProxyManager.GetFunc<int, int, int>(proxy.Func2Method).Access, ProxyManager.LookupFunc<int, int, int>(proxy, "Func2Method").Access, "Func2Method Lookup");
      Assert.AreEqual(ProxyManager.GetFunc<int, int, int, int>(proxy.Func3Method).Access, ProxyManager.LookupFunc<int, int, int, int>(proxy, "Func3Method").Access, "Func3Method Lookup");
      Assert.AreEqual(ProxyManager.GetFunc<int, int, int, int, int>(proxy.Func4Method).Access, ProxyManager.LookupFunc<int, int, int, int, int>(proxy, "Func4Method").Access, "Func4Method Lookup");
      Assert.AreEqual(ProxyManager.GetFunc<int, int, int, int, int, int>(proxy.Func5Method).Access, ProxyManager.LookupFunc<int, int, int, int, int, int>(proxy, "Func5Method").Access, "Func5Method Lookup");

      var getIntConfig = ProxyManager.GetFunc<Dictionary<int, int>, int, int>(proxy.GetInt<int>);
      getIntConfig.IsReal = false;

      Assert.IsFalse(getIntConfig.IsReal);
      getIntConfig.Count = 0;
      Assert.AreEqual(0, getIntConfig.Count);

      proxy.GetInt<int>(null, 12);

      Assert.AreEqual(1, getIntConfig.Count, "GetInt not called as expected");

      var setProp2Config = ProxyManager.GetProp<int>(proxy, (val) => { proxy.Prop2 = val; });
      setProp2Config.IsReal = false;
      setProp2Config = ProxyManager.GetProp<int>(proxy, (val) => { proxy.Prop2 = val; });
      setProp2Config.IsReal = false;

      proxy.Prop2 = 4;

      Assert.AreEqual(4, setProp2Config.Param1);

      ProxyManager.GetProp<int>(proxy, () => { return proxy.Prop2; }).IsReal = false;
      ProxyManager.GetProp<int>(proxy, () => { return proxy.Prop2; }).Return = 1;

      Assert.AreEqual(1, proxy.Prop2);

      var test1Config = ProxyManager.GetFunc<object[], int>(proxy.ArrayTest1);
      var test2Config = ProxyManager.GetFunc<int[], double>(proxy.ArrayTest2);
      var test3Config = ProxyManager.GetFunc<int, double[], object[], int>(proxy.ArrayTest3);

      test1Config.IsReal = false;
      test2Config.IsReal = false;
      test3Config.IsReal = false;

      proxy.ArrayTest1(1, 2, 3, 4, 5);
      proxy.ArrayTest2(new int[] { 1, 2 });
      proxy.ArrayTest3(1, new double[] { 1.2, 3.2 }, 1, 2, 3, "string", "hello");

      Assert.AreEqual(5, test1Config.Param1.Length);
      Assert.AreEqual(2, test2Config.Param1.Length);
      Assert.AreEqual(2, test3Config.Param2.Length);
      Assert.AreEqual(5, test3Config.Param3.Length);
      Assert.AreEqual("hello", test3Config.Param3[4]);

      proxy.T1Event += (d) => { throw new NotImplementedException(); };

      TestUtils.AssertExceptionThrown<NullReferenceException>(() =>
      {
        var result = ProxyManager.CreateProxy<ComplexClassToProxy<int, double>>(null, 4);
      }, "Passing a null to the ProxyManager ctor collection should throw a NullReferenceException");
    }

    [TestMethod]
    public void ConfigureTest()
    {
      var config = new CtorConfig();

      var proxy = ProxyManager.CreateProxy<SimpleClassToProxyTest>(config);

      //not configured should throw
      TestUtils.AssertExceptionThrown<NotImplementedException>(() => { proxy.Function(); }, "not configured");

      config.DefaultBehavior = DefaultBehavior.Fake;

      proxy = ProxyManager.CreateProxy<SimpleClassToProxyTest>(config);

      Assert.AreEqual(0, proxy.Function(), "not configured but should be fake by default");

      config.DefaultBehavior = DefaultBehavior.Real;

      proxy = ProxyManager.CreateProxy<SimpleClassToProxyTest>(config);

      //not configured should throw
      Assert.AreEqual(7, proxy.Function(), "not configured but should be real by default");
    }

    public class SimpleClassToProxyTest
    {
      public virtual int Function()
      {
        return 7;
      }
    }

    [TestMethod]
    public void ProxyCallbackTest()
    {
      var proxy = ProxyManager.CreateProxy<ComplexClassToProxy<string, double>>();

      var actionMethodConfig = ProxyManager.GetAction(proxy.ActionMethod);
      var action1MethodConfig = ProxyManager.GetAction<int>(proxy.Action1Method);
      var action2MethodConfig = ProxyManager.GetAction<int, int>(proxy.Action2Method);
      var action3MethodConfig = ProxyManager.GetAction<int, int, int>(proxy.Action3Method);
      var action4MethodConfig = ProxyManager.GetAction<int, int, int, int>(proxy.Action4Method);
      var action5MethodConfig = ProxyManager.GetAction<int, int, int, int, int>(proxy.Action5Method);

      var funcMethodConfig = ProxyManager.GetFunc<int>(proxy.FuncMethod);
      var func1MethodConfig = ProxyManager.GetFunc<int, int>(proxy.Func1Method);
      var func2MethodConfig = ProxyManager.GetFunc<int, int, int>(proxy.Func2Method);
      var func3MethodConfig = ProxyManager.GetFunc<int, int, int, int>(proxy.Func3Method);
      var func4MethodConfig = ProxyManager.GetFunc<int, int, int, int, int>(proxy.Func4Method);
      var func5MethodConfig = ProxyManager.GetFunc<int, int, int, int, int, int>(proxy.Func5Method);

      int callbackCount = 0;
      actionMethodConfig.Callback = () => { callbackCount++; };
      action1MethodConfig.Callback = (n0) => { callbackCount++; };
      action2MethodConfig.Callback = (n0, n1) => { callbackCount++; };
      action3MethodConfig.Callback = (n0, n1, n2) => { callbackCount++; };
      action4MethodConfig.Callback = (n0, n1, n2, n3) => { callbackCount++; };
      action5MethodConfig.Callback = (n0, n1, n2, n3, n4) => { callbackCount++; };

      funcMethodConfig.Callback = () => { callbackCount++; return -1; };
      func1MethodConfig.Callback = (n0) => { callbackCount++; return -1; };
      func2MethodConfig.Callback = (n0, n1) => { callbackCount++; return -1; };
      func3MethodConfig.Callback = (n0, n1, n2) => { callbackCount++; return -1; };
      func4MethodConfig.Callback = (n0, n1, n2, n3) => { callbackCount++; return -1; };
      func5MethodConfig.Callback = (n0, n1, n2, n3, n4) => { callbackCount++; return -1; };

      proxy.ActionMethod();
      proxy.Action1Method(0);
      proxy.Action2Method(0, 1);
      proxy.Action3Method(0, 1, 2);
      proxy.Action4Method(0, 1, 2, 3);
      proxy.Action5Method(0, 1, 2, 3, 4);

      proxy.FuncMethod();
      proxy.Func1Method(0);
      proxy.Func2Method(0, 1);
      proxy.Func3Method(0, 1, 2);
      proxy.Func4Method(0, 1, 2, 3);
      proxy.Func5Method(0, 1, 2, 3, 4);

      Assert.AreEqual(12, callbackCount, "call back count");

      callbackCount = 0;

      actionMethodConfig.Invoke();
      action1MethodConfig.Invoke(1);
      action2MethodConfig.Invoke(1, 2);
      action3MethodConfig.Invoke(1, 2, 3);
      action4MethodConfig.Invoke(1, 2, 3, 4);
      action5MethodConfig.Invoke(1, 2, 3, 4, 5);

      funcMethodConfig.Invoke();
      func1MethodConfig.Invoke(1);
      func2MethodConfig.Invoke(1, 2);
      func3MethodConfig.Invoke(1, 2, 3);
      func4MethodConfig.Invoke(1, 2, 3, 4);
      func5MethodConfig.Invoke(1, 2, 3, 4, 5);

      Assert.AreEqual(12, callbackCount, "call back count");
    }

    [TestMethod]
    public void LookupTest()
    {
      var proxy = ProxyManager.CreateProxy<ComplexClassToProxy<int, double>>();

      var lookupResult = ProxyManager.LookupMethod<Action<int, int>>(proxy, "Action2Method");
      var getResult = ProxyManager.GetAction<int, int>(proxy.Action2Method);
      Assert.AreEqual(lookupResult.Access, getResult.Access, "LookupMethod return failed");

      TestUtils.AssertExceptionThrown<KeyNotFoundException>(() =>
      {
        ProxyManager.LookupMethod<Action>(proxy, "");
      });

      TestUtils.AssertExceptionThrown<KeyNotFoundException>(() =>
      {
        ProxyManager.LookupMethod<Action<int, double, string>>(proxy, "Poly");
      }, "Too many params");

      TestUtils.AssertExceptionThrown<KeyNotFoundException>(() =>
      {
        ProxyManager.LookupMethod<Action<char>>(proxy, "Poly");
      }, "Type mismatch");

      TestUtils.AssertExceptionThrown<KeyNotFoundException>(() =>
      {
        ProxyManager.LookupMethod<Func<char>>(proxy, "Poly");
      }, "Return Type mismatch");


      TestUtils.AssertExceptionThrown<KeyNotFoundException>(() =>
      {
        ProxyManager.GetAction(proxy.NonVirtualMethod);
      }, "pm get action on non virtual should fail");

      var config = ProxyManager.GetMethod((RefOutType)proxy.RefOut);
      Assert.IsNotNull(config, "ref out delegate should work");

      config.IsReal = false;

      int rint = 3;
      int oint;
      proxy.RefOut(ref rint, out oint);
      Assert.AreEqual(1, config.Count, "ref out should have been called");
      Assert.AreEqual(3, config.Access.GetParameter(0), "ref out param 1");
      Assert.AreEqual(0, config.Access.GetParameter(1), "ref out param 2");

      config.Access.Add(va =>
      {
        va[1] = 134;
        return null;
      });
      proxy.RefOut(ref rint, out oint);
      Assert.AreEqual(2, config.Count, "ref out should have been called");
      Assert.AreEqual(3, config.Access.GetParameter(0), "ref out param 1");
      Assert.AreEqual(134, oint, "ref out param 2 via SetOutParameter");

      config.IsReal = true;

      proxy.RefOut(ref rint, out oint);

      Assert.AreEqual(3, config.Count, "ref out should have been called");
      Assert.AreEqual(rint, oint, "config should use real and assign oint");
    }

    delegate void RefOutType(ref int rint, out int oint);

    [TestMethod]
    public void ProxyBuilderErrorHandlingTest()
    {
      TestUtils.AssertExceptionThrown<ArgumentException>(() => { var proxy = ProxyManager.CreateProxy<PrivateClass>(); }, "Private class is expected to not build");
      TestUtils.AssertExceptionThrown<ArgumentException>(() => { var proxy = ProxyManager.CreateProxy<SealedClass>(); }, "Sealed class is expected to not build");

      var config = new CtorConfig() { DefaultBehavior = DefaultBehavior.Real };
      var p = ProxyManager.CreateProxy<Builder<ClassNotForProxy>>(config);
      ProxyManager.GetFunc<string>(p.BuildProxySourceCode).IsReal = false;
      ProxyManager.GetFunc<string>(p.BuildProxySourceCode).Return = "this will not compile";

      TestUtils.AssertExceptionThrown<ArgumentException>(() =>
      {
        var proxy = p.CreateProxy();
      }, "invalid source code build should throw an arguement exception");

      string expectedResult = "Expected result";

      ProxyManager.GetFunc<bool?[], Type[], string[], string>(p.BuildParams).IsReal = false;
      ProxyManager.GetFunc<bool?[], Type[], string[], string>(p.BuildParams).Return = expectedResult;

      Assert.AreEqual(expectedResult, p.BuildParams(false, false));
    }

    [TestMethod]
    public void NullCtorProxyTest()
    {
      var proxy = ProxyManager.CreateProxy<CtorTestClass>("hello");
      Assert.IsNotNull(proxy);

      var config = new CtorConfig();
      config.DefaultBehavior = DefaultBehavior.Real;
      config.Add<string>(null);

      proxy = ProxyManager.CreateProxy<CtorTestClass>(config);
      Assert.IsNotNull(proxy);
      Assert.IsNull(proxy.Text);
    }

    [TestMethod]
    public void BuildParmsTest()
    {
      var builder = new Builder<ComplexClassToProxy<int, string>>();

      //public virtual string BuildParams(bool?[] refOutMods, Type[] types, string[] names)

      var outMods = new bool?[1] { true };
      var types = new Type[1] { typeof(int) };
      var names = new string[1] { "n" };

      string result;
      result = builder.BuildParams(outMods, types, names);
      Assert.AreEqual("ref global::System.Int32 n", result);
      result = builder.BuildParams(outMods, types, null);
      Assert.AreEqual("ref global::System.Int32", result);
      result = builder.BuildParams(outMods, null, names);
      Assert.AreEqual("ref n", result);
      result = builder.BuildParams(outMods, null, null);
      Assert.AreEqual("", result);
      result = builder.BuildParams(null, types, names);
      Assert.AreEqual("global::System.Int32 n", result);
      result = builder.BuildParams(null, types, null);
      Assert.AreEqual("global::System.Int32", result);
      result = builder.BuildParams(null, null, names);
      Assert.AreEqual("n", result);
      result = builder.BuildParams(null, null, null);
      Assert.AreEqual("", result);
    }

    [TestMethod]
    public void AbstractEventTest()
    {
      var proxy = ProxyManager.CreateProxy<AbstractBaseClass>();
      bool called = false;

      proxy.AbstractAction += () => { called = true; };

      var abstractEventConfig = ProxyManager.LookupEvent<Action>(proxy, "AbstractAction");
      abstractEventConfig.Fire();

      Assert.IsTrue(called, "Event fire failed");
    }

    [TestMethod]
    public void EdgeCaseBuildTest()
    {
      bool called = false;

      //needs Ac()
      var pm1 = ProxyManager.CreateProxy<ClassEvent>();
      pm1.Ac += () => { called = true; };
      ProxyManager.LookupEvent<Action>(pm1, "Ac").Fire();
      Assert.IsTrue(called, "pm1 event fire falled");

      called = false;
      var pm2 = ProxyManager.CreateProxy<InterfaceEvent>();
      pm2.Ac += () => { called = true; };
      ProxyManager.LookupEvent<Action>(pm2, "Ac").Fire();
      Assert.IsTrue(called, "pm2 event fire falled");

      called = false;
      var pm3 = ProxyManager.CreateProxy<AbstractEvent>();
      pm3.Ac += () => { called = true; };
      ProxyManager.LookupEvent<Action>(pm3, "Ac").Fire();
      Assert.IsTrue(called, "pm3 event fire falled");

      called = false;
      var pm4 = ProxyManager.CreateProxy<AbstractInterfaceEvent>();
      pm4.Ac += () => { called = true; };
      ProxyManager.LookupEvent<Action>(pm4, "Ac").Fire();
      Assert.IsTrue(called, "pm4 event fire falled");

      //reflect Ac()
      called = false;
      var pm5 = ProxyManager.CreateProxy<ClassEvent>();
      pm5.Ac += () => { called = true; };
      ProxyManager.LookupEvent<Action>(pm5, "Ac").Fire();
      Assert.IsTrue(called, "pm5 event fire falled");

      called = false;
      var pm6 = ProxyManager.CreateProxy<InInterface>();
      pm6.Ac += () => { called = true; };
      ProxyManager.LookupEvent<Action>(pm6, "Ac").Fire();
      Assert.IsTrue(called, "pm6 event fire falled");

      called = false;
      var pm7 = ProxyManager.CreateProxy<InAbstractParent>();
      pm7.Ac += () => { called = true; };
      ProxyManager.LookupEvent<Action>(pm7, "Ac").Fire();
      Assert.IsTrue(called, "pm7 event fire falled");

      called = false;
      var pm8 = ProxyManager.CreateProxy<InParent>();
      pm8.Ac += () => { called = true; };
      ProxyManager.LookupEvent<Action>(pm8, "Ac").Fire();
      Assert.IsTrue(called, "pm8 event fire falled");

      called = false;
      var pm9 = ProxyManager.CreateProxy<InParentInterfaceEvent>();
      pm9.Ac += () => { called = true; };
      ProxyManager.LookupEvent<Action>(pm9, "Ac").Fire();
      Assert.IsTrue(called, "pm9 event fire falled");

      called = false;
      var pmX = ProxyManager.CreateProxy<ClassVirtualEvent>();
      pmX.Ac += () => { called = true; };
      ProxyManager.LookupEvent<Action>(pmX, "Ac").Fire();
      Assert.IsTrue(called, "pmX event fire falled");
    }

    [TestMethod]
    public void IsInternalTest()
    {
      var builder = new Builder<int>();

      Assert.IsFalse(builder.IsInternal(typeof(int)));
      Assert.IsTrue(builder.IsInternal(typeof(Internal)));
    }

    [TestMethod]
    public void IsInternalPropTest()
    {
      var builder = ProxyManager.CreateProxy<Builder<int>>();

      ProxyManager.GetFunc<PropertyInfo, bool>(builder.IsInternal).IsReal = true;
      var methodInternalConfig = ProxyManager.GetFunc<MethodInfo, bool>(builder.IsInternal);

      var pi = typeof(PropClass).GetProperty("PropInt");

      bool firstCall = true;
      bool firstReturn = false;
      bool secondReturn = false;

      methodInternalConfig.Callback = (mi) =>
      {
        if (firstCall)
        {
          firstCall = false;
          return firstReturn;
        }

        return secondReturn;
      };
      Assert.IsFalse(builder.IsInternal(pi));

      firstCall = true;
      firstReturn = true;
      secondReturn = false;
      Assert.IsTrue(builder.IsInternal(pi));

      firstCall = true;
      firstReturn = false;
      secondReturn = true;
      Assert.IsTrue(builder.IsInternal(pi));

      firstCall = true;
      firstReturn = true;
      secondReturn = true;
      Assert.IsTrue(builder.IsInternal(pi));
    }

    [TestMethod]
    public void IsInternalMethodInfoTest()
    {
      var builder = ProxyManager.CreateProxy<Builder<int>>();

      var isInternalConfig = ProxyManager.GetFunc<MethodInfo, bool>(builder.IsInternal);
      isInternalConfig.IsReal = true;

      Assert.IsFalse(isInternalConfig.Invoke(null));

      var isInternalTypeConfig = ProxyManager.GetFunc<Type, bool>(builder.IsInternal);
      isInternalTypeConfig.Return = true;
      var method = typeof(PropClass).GetMethod("Method");

      Assert.IsTrue(isInternalConfig.Invoke(method));

      bool first = true;
      isInternalTypeConfig.Callback = (t) =>
      {
        if (first)
        {
          first = false;
          return false;
        }
        return true;
      };

      Assert.IsTrue(isInternalConfig.Invoke(method));
    }

    [TestMethod]
    public void InterfaceTest()
    {
      var pm1 = ProxyManager.CreateProxy<InterfaceOne>();
      ProxyManager.GetAction(pm1.MethodOne).IsReal = false;
      var pm2 = ProxyManager.CreateProxy<InterfaceTwo>();
      ProxyManager.GetAction(pm2.MethodOne).IsReal = false;
      ProxyManager.GetAction(pm2.MethodTwo).IsReal = false;
      var pm3 = ProxyManager.CreateProxy<AbstractOne>();

      TestUtils.AssertExceptionThrown<KeyNotFoundException>(() =>
      {
        ProxyManager.GetAction(pm3.MethodOne).IsReal = false;
      }, "MethodOne should not be configured because it is not virtual");

      ProxyManager.GetAction(pm3.MethodTwo).IsReal = false;
      ProxyManager.GetAction(pm3.MethodThree).IsReal = false;
      ProxyManager.GetAction(pm3.MethodFour).IsReal = false;

      TestUtils.AssertExceptionThrown<KeyNotFoundException>(() =>
      {
        ProxyManager.GetAction(pm3.MethodFive).IsReal = false;
      }, "MethodFive should not be configured because it is not virtual");

      var pm4 = ProxyManager.CreateProxy<AbstractTwo>();
      ProxyManager.GetAction(pm4.MethodFour).IsReal = false;
    }

    [TestMethod]
    public void HardTemplatesTest()
    {
      var proxy = ProxyManager.CreateProxy<HardTemplatesClass<Dictionary<string, double>>>();

      ProxyManager.GetFunc<int, int, int>(proxy.Foo<int>).IsReal = true;

      Assert.AreEqual(77, proxy.Foo<int>(77, 6));

      ProxyManager.GetFunc<int, int>(proxy.Foo<int>).IsReal = true;
      Assert.AreEqual(7, proxy.Foo<int>(7));

      ProxyManager.GetFunc<int, HardTemplatesClass<int>, List<int>, int>(proxy.Foo<int>).IsReal = true;
      Assert.AreEqual(777, proxy.Foo<int>(3, new HardTemplatesClass<int>(), new List<int>() { 777 }));

      ProxyManager.GetFunc<int, Dictionary<string, double>, ComplexClassToProxy<int, Dictionary<string, double>>>(proxy.FH<int>).IsReal = true;
      var ret = new ComplexClassToProxy<char, Dictionary<string, double>>();
      ProxyManager.GetFunc<char, Dictionary<string, double>, ComplexClassToProxy<char, Dictionary<string, double>>>(proxy.FH<char>).Return = ret;

      Assert.IsNotNull(proxy.FH<int>(1, null));
      Assert.AreEqual(ret, proxy.FH<char>('1', null), "faked ret not used");
    }

    [TestMethod]
    public void HardTemplatesLookupTest()
    {
      var proxy = ProxyManager.CreateProxy<HardTemplatesClass<Dictionary<string, double>>>();

      ProxyManager.LookupFunc<int, int, int>(proxy, "Foo").IsReal = true;

      Assert.AreEqual(77, proxy.Foo<int>(77, 6));

      ProxyManager.LookupFunc<int, int>(proxy, "Foo", typeof(int)).IsReal = true;
      Assert.AreEqual(7, proxy.Foo<int>(7));

      ProxyManager.LookupFunc<int, HardTemplatesClass<int>, List<int>, int>(proxy, "Foo", typeof(int)).IsReal = true;
      Assert.AreEqual(777, proxy.Foo<int>(3, new HardTemplatesClass<int>(), new List<int>() { 777 }));

      ProxyManager.LookupFunc<int, Dictionary<string, double>, ComplexClassToProxy<int, Dictionary<string, double>>>
        (proxy, "FH", typeof(int)).IsReal = true;
      var ret = new ComplexClassToProxy<char, Dictionary<string, double>>();
      ProxyManager.LookupFunc<char, Dictionary<string, double>, ComplexClassToProxy<char, Dictionary<string, double>>>
        (proxy, "FH", typeof(char)).Return = ret;

      Assert.IsNotNull(proxy.FH<int>(1, null));
      Assert.AreEqual(ret, proxy.FH<char>('1', null), "faked ret not used");
    }

    [TestMethod]
    public void ValueTypePropTest()
    {
      var proxy = ProxyManager.CreateProxy<HasValueType>();
      ProxyManager.GetProp<ValueType>(proxy, (v) => { proxy.ValueType = v; });
    }
  }

  public class HasValueType
  {
    public virtual ValueType ValueType { get; set; }
  }

  public struct ValueType
  {
  }

  public class HardTemplatesClass<H>
  {
    public virtual int Foo<F>(int i)
    {
      return i;
    }

    public virtual F Foo<F>(F f, int i)
    {
      return f;
    }

    public virtual F Foo<F>(F f, HardTemplatesClass<F> i, List<F> fs)
    {
      return i.Foo<F>(fs[0], 0);
    }

    public virtual ComplexClassToProxy<F, H> FH<F>(F f, H h)
    {
      return new ComplexClassToProxy<F, H>();
    }
  }
  public interface InterfaceOne
  {
    void MethodOne();
    int PropOne { set; }
    event Action EventOne;
  }
  public interface InterfaceTwo : InterfaceOne
  {
    void MethodTwo();
    int PropTwo { get; }
    event Action EventTwo;
  }
  public abstract class AbstractOne : InterfaceTwo
  {
    public void MethodOne()
    {
    }
    public int PropOne { set { } }
    public event Action EventOne;

    public virtual void MethodTwo()
    {
    }
    public virtual int PropTwo { get { return 1; } }
    public virtual event Action EventTwo;

    public virtual void MethodThree()
    {
    }
    public virtual int PropThree { get; set; }
    public virtual event Action EventThree;

    public abstract void MethodFour();
    public abstract int PropFour { get; set; }
    public abstract event Action EventFour;

    public void MethodFive()
    {
    }
    public int PropFive { get; set; }
    public event Action EventFive;

    private void Use()
    {
      EventOne();
      EventTwo();
      EventThree();
      EventFive();
    }
  }
  public abstract class AbstractTwo : AbstractOne
  {
    public override void MethodFour()
    {
      throw new NotImplementedException();
    }

    public override int PropFour
    {
      get
      {
        throw new NotImplementedException();
      }
      set
      {
        throw new NotImplementedException();
      }
    }

    public override event Action EventFour;

    private void Use()
    {
      EventFour();
    }
  }

  public class PropClass
  {
    public int PropInt { get; set; }
    public void Method(int i) { }
  }
  internal class Internal
  {
    internal int PropInt { get; set; }
  }
  public interface InterfaceEvent
  {
    event Action Ac;
  }
  public abstract class AbstractEvent
  {
    public abstract event Action Ac;
    public event Action Ac2;

    private void Use()
    {
      Ac2();
    }
  }
  public class ClassEvent
  {
    public event Action Ac;

    private void Use()
    {
      Ac();
    }
  }
  public class ClassVirtualEvent
  {
    public virtual event Action Ac;
    private void Use()
    {
      Ac();
    }
  }
  public abstract class AbstractInterfaceEvent : InterfaceEvent
  {
    public abstract event Action Ac;
    public event Action Ac2;

    private void Use()
    {
      Ac2();
    }
  }
  public class InInterface : InterfaceEvent
  {
    public event Action Ac;

    private void Use()
    {
      Ac();
    }
  }
  public class InAbstractParent : AbstractEvent
  {
    public override event Action Ac;

    private void Use()
    {
      Ac();
    }
  }
  public class InParent : ClassEvent
  {
  }
  public class InParentInterfaceEvent : ClassEvent, InterfaceEvent
  {
  }

  public class CtorTestClass
  {
    public string Text { get; set; }
    public CtorTestClass(string text)
    {
      Text = text;
    }
  }

  public class ClassNotForProxy { }

  class PrivateClass { }
  sealed class SealedClass { }

  public abstract class AbstractBaseClass
  {
    public abstract event Action AbstractAction;

    public abstract void Foo();
  }

  public class AbstractChild : AbstractBaseClass
  {
    public override event Action AbstractAction;

    public override void Foo()
    {
      throw new NotImplementedException();
    }

    private void Use()
    {
      AbstractAction();
    }
  }

  public interface IStuff
  {
    void DoIt();
  }

  public class Stuff : AbstractChild, IStuff
  {
    public virtual void DoIt()
    {
      throw new NotImplementedException();
    }
  }


  public class ComplexClassToProxy<T1, T2> : AbstractBaseClass, EventInterface<T1, T2>
  {
    public virtual IList SimpleMap { get; protected set; }
    public virtual Dictionary<int, long> Keyedmap { protected get; set; }
    public IList NonVirtual { get; set; }

    public virtual int GetInt<T>(Dictionary<int, int> ints, T t)
    {
      throw new NotImplementedException();
    }

    public virtual void Throws()
    {
      throw new NotImplementedException();
    }

    public void NonVirtualMethod() { }
    protected virtual void ProtVirtual() { }

    public int Prop1 { get; set; }
    public virtual int Prop2 { get; set; }
    public virtual int Prop3 { get; protected set; }
    public virtual int Prop4 { protected get; set; }
    public virtual int Prop5 { get; private set; }
    public virtual int Prop6 { private get; set; }
    protected virtual int Prop7 { get; private set; }
    protected virtual int Prop8 { private get; set; }
    private int Prop9 { get; set; }

    protected virtual int Prop10 { get; set; }
    protected virtual int Prop11 { get { throw new NotImplementedException(); } }
    protected virtual int Prop12 { set { throw new NotImplementedException(); } }
    public int Prop13 { get { throw new NotImplementedException(); } }
    public int Prop14 { set { throw new NotImplementedException(); } }

    public virtual NamespaceA.CommonClass CommonClassA { get; set; }
    public virtual NamespaceB.CommonClass CommonClassB { get; set; }
    public virtual CommonClass CommonClassC { get; set; }

    public virtual int PublicVirtualMethod() { throw new NotImplementedException(); }
    protected virtual void ProtectedVirtualMethod() { }
    private void PrivateMethod() { }

    public virtual void ActionMethod() { throw new NotImplementedException(); }
    public virtual void Action1Method(int i) { throw new NotImplementedException(); }
    public virtual void Action2Method(int i, int j) { throw new NotImplementedException(); }
    public virtual void Action3Method(int i, int j, int k) { throw new NotImplementedException(); }
    public virtual void Action4Method(int i, int j, int k, int m) { throw new NotImplementedException(); }
    public virtual void Action5Method(int i, int j, int k, int m, int n) { throw new NotImplementedException(); }

    public virtual int FuncMethod() { throw new NotImplementedException(); }
    public virtual int Func1Method(int i) { throw new NotImplementedException(); }
    public virtual int Func2Method(int i, int j) { throw new NotImplementedException(); }
    public virtual int Func3Method(int i, int j, int k) { throw new NotImplementedException(); }
    public virtual int Func4Method(int i, int j, int k, int m) { throw new NotImplementedException(); }
    public virtual int Func5Method(int i, int j, int k, int m, int n) { throw new NotImplementedException(); }

    public event Action ActionEvent;
    public override event Action AbstractAction;

    private void Use()
    {
      ActionEvent();
      AbstractAction();
    }

    public ComplexClassToProxy(NamespaceA.CommonClass a, NamespaceB.CommonClass b, CommonClass c)
    {
      CommonClassA = a;
      CommonClassB = b;
      CommonClassC = c;
    }

    public ComplexClassToProxy()
    {
      T1Event += (t2) => { throw new NotImplementedException(); };
    }

    public class CommonClass
    { }

    public virtual T1 ArrayTest1(params object[] args)
    {
      throw new NotImplementedException();
    }

    public virtual T2 ArrayTest2(T1[] t1s)
    {
      throw new NotImplementedException();
    }

    public virtual T1 ArrayTest3(T1 t1, T2[] t2s, params object[] args)
    {
      throw new NotImplementedException();
    }

    public delegate T1 T1Type(T2 t2);
    public virtual event T1Type T1Event;

    public virtual void Poly()
    { }
    public virtual void Poly(int i)
    { }
    public virtual void Poly(int i, double d)
    { }
    public virtual void Poly(string s, string t)
    { }

    public virtual void RefOut(ref int rint, out int oint)
    {
      oint = rint;
    }

    public override void Foo()
    {
      throw new NotImplementedException();
    }
  }

  public interface EventInterface<T1, T2>
  {
    event ComplexClassToProxy<T1, T2>.T1Type T1Event;
  }

  namespace NamespaceA
  {
    public class CommonClass
    { }
  }

  namespace NamespaceB
  {
    public class CommonClass
    { }
  }

  public static class PropertiesExtension
  {
    public static IEnumerable<PropValue> PropValues(this object obj)
    {
      if (obj == null) yield break;
      foreach (var pi in obj.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
      {
        var get = pi.GetGetMethod(false);
        if (get == null) continue;

        object result = null;
        try
        {
          result = get.Invoke(obj, new object[] { });
        }
        catch
        {
          continue;
        }

        yield return new PropValue() { Name = pi.Name, Type = pi.PropertyType, Value = result };
      }
    }

    public class PropValue
    {
      public string Name { get; set; }
      public Type Type { get; set; }
      public object Value { get; set; }

      public override int GetHashCode()
      {
        return base.GetHashCode();
      }

      public override string ToString()
      {
        return string.Format("{0}[{1}]: {2}", Name, Type, Value);
      }

      public override bool Equals(object obj)
      {
        if (obj == null || obj.GetType() != typeof(PropValue)) return false;

        var it = (PropValue)obj;

        return
          Name == it.Name &&
          Type == it.Type &&
          Value.Equals(it.Value);
      }
    }

    public static IEnumerable<PropValue> Where(this IEnumerable<PropValue> props, object value)
    {
      foreach (PropValue prop in props)
      {
        if (prop.Value == null && value == null) yield return prop;

        if (prop.Value == null || value == null) continue;

        if (prop.Type != value.GetType()) continue;

        if (prop.Value.Equals(value)) yield return prop;
      }
    }
  }
}


/////////////////////////////////////////////////////////////////////////////////////////

